package 加密与安全;

/*
对称加密算法就是传统的用一个密码进行加密和解密。 Symmetric-key algorithm  对称key算法
从程序的角度看，所谓加密，就是这样一个函数，它接收密码和明文，然后输出密文：
secret = encrypt(key, message);

而解密则相反，它接收密码和密文，然后输出明文：
plain = decrypt(key, secret);

在软件开发中，常用的对称加密算法有：

算法	   密钥长度       工作模式                填充模式
DES	56/64       ECB/CBC/PCBC/CTR/...	 NoPadding/PKCS5Padding/...
AES	128/192/256	ECB/CBC/PCBC/CTR/...	 NoPadding/PKCS5Padding/PKCS7Padding/...
IDEA	128         ECB	                PKCS5Padding/PKCS7Padding/...
密钥长度直接决定加密强度，而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。Java标准库提供的算法实现并不包括所有的工作模式
和所有填充模式，但是通常我们只需要挑选常用的使用就可以了。

最后注意，DES算法由于密钥过短，可以在短时间内被暴力破解，所以现在已经不安全了。

常见加密算法：
DES : Data Encryption Standard，即数据加密标准，是一种使用密钥加密的块算法，1977年被美 国联邦政府的国家标准局确定为联邦
		资料处理标准（FIPS），并授权在非密级政府通信中使用，随后该算法在国际上广泛流传开来。
AES : Advanced Encryption Standard, 高级加密标准 .在密码学中又称Rijndael加密法，是美国联邦政府采用的一种区块加密标
		准。这个标准用来替代原先的DES，已经被多方分析且广为全世界所使用。

特点：
  -加密速度快, 可以加密大文件。
  -密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露。
  -加密后编码表找不到对应字符, 出现乱码，一般结合Base64使用。

加密模式：

	ECB : Electronic codebook, 电子密码本. 需要加密的消息按照块密码的块大小被分为数个块，并对每个块进行独立加密。
		--优点 : 可以并行处理加密数据，速度快。
		--缺点 : 同样的原文生成同样的密文, 不能很好的保护数据。

	CBC : Cipher-block chaining, 密码块链接. 每个明文块先与前一个密文块进行异或后，再进行加密。在这种方法中，每个密文块
			都依赖于它前面的所有明文块。
		--优点 : 同样的原文生成的密文不一样。(它需要一个随机数作为IV参数)
		--缺点 : 串行处理数据，速度较慢。

填充模式
	当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则，分为NoPadding和PKCS5Padding。

	NoPadding：
		--不填充。
		--在DES加密算法下, 要求原文长度必须是8byte的整数倍。
		--在AES加密算法下, 要求原文长度必须是16byte的整数倍。
	PKCS5Padding：
		--数据块的大小为8位, 不够就补足
	注意：
		--默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
		--如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 
		  初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());



【使用AES加密】
AES算法是目前应用最广泛的加密算法。我们先用ECB模式加密并解密：
*/
//import java.security.*;
import java.util.Base64;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
//import java.util.Arrays;

//import javax.crypto.*;
//import javax.crypto.spec.*;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/*
public class 对称加密法 {
	public static void main(String[] args) throws Exception {
      // 原文:
      String message = "Hello, world!";
      System.out.println("Message: " + message);
      //1.直接指定密钥
      // 128位密钥 = 16 bytes Key:  必须是16个字节128位密钥
      //byte[] key = "1234567890abcdef".getBytes("UTF-8");
      
      //2.通过密钥生成器生成密钥
      KeyGenerator keyGerenator=KeyGenerator.getInstance("AES");//密钥生成器实例
      keyGerenator.init(128);//初始化密钥生成器为128位16个字节数
      SecretKey secretKey=keyGerenator.generateKey();//生成密钥
      //byte[] keyBytes=secretKey.getEncoded();//获取密钥字节数组
      byte[] key=secretKey.getEncoded();//获取密钥字节数组
      
      
      System.out.println("key: " + Arrays.toString(key));//[49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 97, 98, 99, 100, 101, 102]
      // 加密:
      byte[] data = message.getBytes("UTF-8");
      byte[] encrypted = encrypt(key, data);
      System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
      // 解密:
      byte[] decrypted = decrypt(key, encrypted);
      System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
  }

  // 加密:
  public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
      Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//  算法/工作模式/填充模式
      SecretKey keySpec = new SecretKeySpec(key, "AES");
      cipher.init(Cipher.ENCRYPT_MODE, keySpec);
      return cipher.doFinal(input);
  }

  // 解密:
  public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
      Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//根据算法名称/工作模式/填充模式获取Cipher()实例
      SecretKey keySpec = new SecretKeySpec(key, "AES");//根据算法名称初始化一个SecretKey实例，密钥必须是指定长度；
      cipher.init(Cipher.DECRYPT_MODE, keySpec);//使用SecretKey初始化Cipher实例，并设置加密或解密模式；
      return cipher.doFinal(input);//传入明文或密文，获得密文或明文。
  }
}
*/
/*
Java标准库提供的对称加密接口非常简单，使用时按以下步骤编写代码：
	1.根据算法名称/工作模式/填充模式获取Cipher()实例；  Cipher:密码的意思
	2.根据算法名称初始化一个SecretKey实例，密钥必须是指定长度；
	3.使用SecretKey初始化Cipher实例，并设置加密或解密模式；
	4.传入明文或密文，获得密文或明文。

ECB模式是最简单的AES加密模式，它只需要一个固定长度的密钥，固定的明文会生成固定的密文，这种一对一的加密方式会导致安全性降低，更好的
方式是通过CBC模式，它需要一个随机数作为IV参数，这样对于同一份明文，每次生成的密文都不同：	
*/

//CBC模式
public class 对称加密法 {
   public static void main(String[] args) throws Exception {
       // 原文:
       String message = "Hello, world!";
       System.out.println("Message: " + message);
       //直接指定密钥
       // 256位密钥 = 32 bytes Key:
       //byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
       
       //2.通过密钥生成器生成密钥
       KeyGenerator keyGerenator=KeyGenerator.getInstance("AES");//密钥生成器实例
       keyGerenator.init(128);//初始化密钥生成器为128位16个字节数
       SecretKey secretKey=keyGerenator.generateKey();//生成密钥
       //byte[] keyBytes=secretKey.getEncoded();//获取密钥字节数组
       byte[] key=secretKey.getEncoded();//获取密钥字节数组
       
       // 加密:
       byte[] data = message.getBytes("UTF-8");
       byte[] encrypted = encrypt(key, data);
       System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
       // 解密:
       byte[] decrypted = decrypt(key, encrypted);
       System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
   }

   // 加密:
   public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
       Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//AES算法、CBC模式、PKCS5Padding填充模式
       SecretKeySpec keySpec = new SecretKeySpec(key, "AES");//AES算法
       byte[] x=keySpec.getEncoded();
       // CBC模式需要生成一个16 bytes的initialization vector:
       SecureRandom sr = SecureRandom.getInstanceStrong();
       byte[] iv = sr.generateSeed(16);
       IvParameterSpec ivps = new IvParameterSpec(iv);
       cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
       byte[] data = cipher.doFinal(input);
       // IV不需要保密，把IV和密文一起返回，join:连接数组
       return join(iv, data);
   }

   // 解密:
   public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
       // 把input分割成IV和密文:
       byte[] iv = new byte[16];
       byte[] data = new byte[input.length - 16];
       //arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
       System.arraycopy(input, 0, iv, 0, 16);//从0下标开始拷贝input至iv的下标0至下标16 
       System.arraycopy(input, 16, data, 0, data.length);
       // 解密:
       Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
       SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
       IvParameterSpec ivps = new IvParameterSpec(iv);
       cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
       return cipher.doFinal(data);
   }

   public static byte[] join(byte[] bs1, byte[] bs2) {
       byte[] r = new byte[bs1.length + bs2.length];
       System.arraycopy(bs1, 0, r, 0, bs1.length);
       System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
       return r;
   }
}

/*
在CBC模式下，需要一个随机生成的16字节IV参数，必须使用SecureRandom生成。因为多了一个IvParameterSpec实例，因此，初始化
方法需要调用Cipher的一个重载方法并传入IvParameterSpec。

观察输出，可以发现每次生成的IV不同，密文也不同。
*/